package com.ceair.authorization.wechat;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @author wangbaohai
 * @ClassName WechatMapAccessTokenResponseConverter
 * @description: 微信登录获取token的响应处理类
 * @date 2024年11月27日
 * @version: 1.0.0
 */
public class WechatMapAccessTokenResponseConverter implements Converter<Map<String, Object>,
        OAuth2AccessTokenResponse> {

    private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = new HashSet<>(
            Arrays.asList(OAuth2ParameterNames.ACCESS_TOKEN, OAuth2ParameterNames.EXPIRES_IN,
                    OAuth2ParameterNames.REFRESH_TOKEN, OAuth2ParameterNames.SCOPE, OAuth2ParameterNames.TOKEN_TYPE));

    /**
     * 从给定的令牌响应参数中获取指定参数的值
     *
     * @param tokenResponseParameters 一个包含令牌响应参数的映射，其中键为参数名，值为参数值
     * @param parameterName           需要获取值的参数名称
     * @return 参数值的字符串表示，如果指定参数不存在或其值为null，则返回null
     */
    private static String getParameterValue(Map<String, Object> tokenResponseParameters, String parameterName) {
        // 从映射中获取指定参数名对应的值
        Object obj = tokenResponseParameters.get(parameterName);
        // 如果值不为null，则转换为字符串返回；否则返回null
        return (obj != null) ? obj.toString() : null;
    }

    /**
     * 从令牌响应参数中获取访问令牌类型
     *
     * @param tokenResponseParameters 令牌响应参数映射，包含令牌类型信息
     * @return 如果令牌类型为“bearer”，则返回 OAuth2AccessToken.TokenType.BEARER，否则返回 null
     */
    private static OAuth2AccessToken.TokenType getAccessTokenType(Map<String, Object> tokenResponseParameters) {
        // 检查令牌响应参数中的令牌类型是否为“bearer”
        if (OAuth2AccessToken.TokenType.BEARER.getValue()
                .equalsIgnoreCase(getParameterValue(tokenResponseParameters, OAuth2ParameterNames.TOKEN_TYPE))) {
            // 如果是，则返回 BEARER 类型
            return OAuth2AccessToken.TokenType.BEARER;
        }
        // 如果不是，则返回 null
        return null;
    }

    /**
     * 从令牌响应参数中获取过期时间
     *
     * @param tokenResponseParameters 包含令牌响应参数的映射
     * @return 过期时间的值，如果参数不存在或无法转换为长整型，则返回0L
     */
    private static long getExpiresIn(Map<String, Object> tokenResponseParameters) {
        return getParameterValue(tokenResponseParameters);
    }

    /**
     * 从令牌响应参数中获取指定名称的参数值
     * 如果指定名称的参数不存在，或者其值无法转换为长整型，则返回默认值
     *
     * @param tokenResponseParameters 令牌响应参数，一个映射表，键为参数名，值为参数值
     * @return 长整型参数值，如果获取失败则返回默认值
     */
    private static long getParameterValue(Map<String, Object> tokenResponseParameters) {
        // 初始化参数值为默认值
        long parameterValue = 0L;

        // 尝试从参数映射表中获取指定名称的参数值
        Object obj = tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN);
        if (obj != null) {
            // 如果参数值类型为Long，则直接赋值
            if (obj.getClass() == Long.class) {
                parameterValue = (Long) obj;
            }
            // 如果参数值类型为Integer，则转换为长整型后赋值
            else if (obj.getClass() == Integer.class) {
                parameterValue = (Integer) obj;
            } else {
                // 如果参数值类型既非Long也非Integer，则尝试将其字符串形式转换为长整型
                try {
                    parameterValue = Long.parseLong(obj.toString());
                } catch (NumberFormatException ignored) {
                    // 如果转换失败，则保持参数值为默认值
                }
            }
        }

        // 返回最终的参数值
        return parameterValue;
    }

    /**
     * 从令牌响应参数中提取作用域集合
     *
     * @param tokenResponseParameters 包含令牌响应参数的映射
     * @return 作用域的集合如果参数中不包含作用域信息，则返回空集合
     */
    private static Set<String> getScopes(Map<String, Object> tokenResponseParameters) {
        // 检查令牌响应参数中是否包含作用域信息
        if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
            // 获取作用域参数值
            String scope = getParameterValue(tokenResponseParameters, OAuth2ParameterNames.SCOPE);
            // 将作用域字符串分割成数组，并转换为集合
            return new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
        }
        // 如果参数中不包含作用域信息，返回空集合
        return Collections.emptySet();
    }

    /**
     * 将给定的源数据转换为OAuth2AccessTokenResponse对象
     * 此方法处理的源数据是一个键值对映射，从中提取访问令牌、刷新令牌等信息，
     * 并将其封装到一个OAuth2AccessTokenResponse对象中
     *
     * @param source 包含访问令牌响应参数的源映射
     * @return 从源数据转换得到的OAuth2AccessTokenResponse对象
     */
    @Override
    public OAuth2AccessTokenResponse convert(Map<String, Object> source) {
        // 从源映射中提取访问令牌
        String accessToken = getParameterValue(source, OAuth2ParameterNames.ACCESS_TOKEN);
        // 从源映射中提取令牌类型
        OAuth2AccessToken.TokenType accessTokenType = getAccessTokenType(source);
        // 从源映射中提取令牌过期时间
        long expiresIn = getExpiresIn(source);
        // 从源映射中提取作用域
        Set<String> scopes = getScopes(source);
        // 从源映射中提取刷新令牌
        String refreshToken = getParameterValue(source, OAuth2ParameterNames.REFRESH_TOKEN);
        // 初始化额外参数映射，用于存储除标准参数外的其他所有参数
        Map<String, Object> additionalParameters = new LinkedHashMap<>();
        // 遍历源映射中的所有条目，将非标准参数添加到额外参数映射中
        for (Map.Entry<String, Object> entry : source.entrySet()) {
            if (!TOKEN_RESPONSE_PARAMETER_NAMES.contains(entry.getKey())) {
                additionalParameters.put(entry.getKey(), entry.getValue());
            }
        }
        // 使用提取的参数构建并返回OAuth2AccessTokenResponse对象
        // 这里为tokenType提供一个默认值，如果源映射中未指定的话
        return OAuth2AccessTokenResponse.withToken(accessToken)
                .tokenType(accessTokenType == null ? OAuth2AccessToken.TokenType.BEARER : accessTokenType)
                .expiresIn(expiresIn)
                .scopes(scopes)
                .refreshToken(refreshToken)
                .additionalParameters(additionalParameters)
                .build();
    }

}
